{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s?\\_\\_biz=MzU0OTg3NzU2NA==&mid=2247484908&idx=1&sn=64c49a211286ba9f1113ee0a75b5aef0&chksm=fba867bfccdfeea95fd2dca7a6de43cca02db4413347f3a5ef46652e20b68fdd4a0ca4f4ca60&scene=21#wechat\\_redirect)\n",
    "\n",
    "很多新手面对 docker 一脸茫然，于是我想按照自己的理解帮助新手快速入门 docker，便有了下文。\n",
    "\n",
    "如果你了解虚拟机，那么可以对比着理解，计算机的世界里，死记硬背是行不通的，必须理解，只有深刻理解了的工具才能更好的使用它。docker 就是一个进化后的虚拟机，比虚拟机更轻巧，更灵活，更节约系统资源。\n",
    "\n",
    "如果不了解虚拟机，可以把它比作程序的集装箱，把程序及运行环境都放在集装箱里面，无需拆卸集装箱程序就可以运行，把这个集装箱迁移到任何地方，程序都可以运行，无需额外的环境配置（不需要拆箱）。有了 docker，所有的程序都只要是以 docker 形式发布，就都是完全跨平台的了。\n",
    "\n",
    "所有说 docker 才是 IT 系统的基础设施，值得每个程序员去学习和使用。\n",
    "\n",
    "虚拟机有个软件叫 vmware workstation，对应的，docker 有个软件叫 docker desktop（社区版），这两个软件都是提供虚拟化的基石。对于个人电脑，必须安装 docker desktop 才能使用 docker，在搜索引擎一搜，从官网下载即可，目前 windows，mac，linux 都可以安装 docker desktop。\n",
    "\n",
    "#### 步骤 0: linux 非 root 用户使用 docker\n",
    "\n",
    "如果是 linux，可执行下述命令自动下载和安装，安装成功后已默认设置开机启动并自动启动。\n",
    "\n",
    "```\n",
    "$ wget -qO- https://get.docker.com/ | sh\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "执行后，会有一个提示，意思是非 root 用户如果想直接运行 docker，需要将该用户添加到 docker 用户组中，然后重新登陆。\n",
    "\n",
    "```\n",
    "$ sudo usermod -aG docker aaron  #这里的aaron是用户名\n",
    "$ #然后重新登陆\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "mac 和 windows 直接从官网下载安装即可。\n",
    "\n",
    "假定现在你已经安装了 docker desktop。\n",
    "\n",
    "安装完后，打开终端，输入 docker 命令回车，你会看到 docker 所支持的命令及简洁的帮助文档，如果英文好一点，帮助文档就可以带你入门，具体的命令可以执行 `docker help 命令` 的形式查看其帮助信息，这个技能必须会，比用的时候再去网上找样例要高效的多。请牢记这一点，现在如果看不懂没关系，假以时日，你自然能看懂帮助信息。\n",
    "\n",
    "接下来干什么？先明确用 docker 干什么，假如，你想安装一个 docker 版的 ubuntu 玩一玩。\n",
    "\n",
    "#### 步骤 1：下载镜像文件\n",
    "\n",
    "如果你用虚拟机的话，你需要下载 ubuntu 的 iso 文件，然后再用虚拟机安装这个 iso，最后启动 ubuntu。这个 iso 文件，我们叫它操作系统的镜像，就是镜像文件。\n",
    "\n",
    "同理，如果用 docker 的话，也需要下下载 docker 版的 ubuntu 镜像（英文叫 image），这个镜像文件比虚拟机的 iso 文件要小的多，而且下载后不需要安装，直接启动就行了，凭这一点，我就已经抛弃虚拟机了。\n",
    "\n",
    "那么接下来我去哪里找 docker 版的 ubuntu 镜像呢？很简单，docker 已经为你想好了，那就是 search 命令，执行 `docker search ubuntu` 看看，一般情况下选择第一个即可，或者根据 star 的数量、官方标志来选择，记住选择的镜像名字，这里是 ubuntu。\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialUzJBnEl7AtViauWlnibYxzsevVICb3OuwlmmtxF45EgiaDl5gG9RZ0fg1g/640?wx_fmt=jpeg)\n",
    "\n",
    "docker search ubuntu\n",
    "\n",
    "确定后我们执行 `docker pull ubuntu` 即可下载此 ubuntu 镜像:\n",
    "\n",
    "下载过程如下：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker pull ubuntu\n",
    "Using default tag: latest\n",
    "latest: Pulling from library/ubuntu\n",
    "e6ca3592b144: Pull complete\n",
    "534a5505201d: Pull complete\n",
    "990916bd23bb: Pull complete\n",
    "Digest: sha256:cbcf86d7781dbb3a6aa2bcea25403f6b0b443e20b9959165cf52d2cc9608e4b9\n",
    "Status: Downloaded newer image for ubuntu:latest\n",
    "docker.io/library/ubuntu:latest\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "下载过程中，你可以再开个窗口，执行 docker help pull 看看此命令的帮助文档，看看描述能否理解：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker help pull\n",
    "\n",
    "Usage:    docker pull \\[OPTIONS\\] NAME\\[:TAG|@DIGEST\\]\n",
    "\n",
    "Pull an image or a repository from a registry\n",
    "\n",
    "Options:\n",
    "  -a, --all-tags                Download all tagged images in the repository\n",
    "      --disable-content-trust   Skip image verification (default true)\n",
    "      --platform string         Set platform if server is multi-platform\n",
    "                                capable\n",
    "  -q, --quiet                   Suppress verbose output\n",
    "(py38env) ➜  ~\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "比如这里的 `Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]`，可以看到中括号包起来的是可选的，没有包起来的都是必输的，名称后面可以加个冒号和标签，就可以下载该名称下某个具体标签的镜像，如果不写 tag，那就默认下载最新的。\n",
    "\n",
    "至此，镜像文件已经下载完成。\n",
    "\n",
    "#### 步骤 2：查看镜像文件，修改镜像存放位置\n",
    "\n",
    "查看自己下载了哪些镜像呢，执行 `docker images` ：\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialU7KaaRlsXXFuzbaMwf1lSsds2fmuZiaZFQ8KBcTZDLElt9sCYDjQaaTQ/640?wx_fmt=jpeg)\n",
    "\n",
    "docker images\n",
    "\n",
    "从上图看到，镜像文件有的还挺大，但仍然比完整的操作系统镜像要小，他们存储在磁盘的什么位置呢？默认情况下：\n",
    "\n",
    "如果是 mac，镜像文件保存在 `~/Library/Containers/com.docker.docker` 路径下，\n",
    "\n",
    "如果是 linux，可通过 `docker info` 命令查看 Docker 默认的存储路径，通常位于：`/var/lib/docker`:\n",
    "\n",
    "```\n",
    "sudo docker info | grep \"Docker Root Dir\"\n",
    "# Docker Root Dir: /var/lib/docker\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "docker info 还可以查看容器的池、已用数据大小、总数据大小，基本容器大小、当前运行容器数量等。\n",
    "\n",
    "随着镜像文件越来越多，占有空间越来越大，你可能会产生想把镜像文件存放在其他位置的想法：\n",
    "\n",
    "**如果是 mac** 第一种方法是通过修改 docker 的配置来实现：点击顶部菜单栏中的 docker 图标 -> Preferences -> Resources -> ADVANCED 拖到最后，Disk image location，如下图，然后点击 browse 来将 imgae 保存到新的位置。\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialUojEmeXiaoEpuJnXELbtY73wSDMwc2gBwMawvk1KbMExDpm5VheqicBicQ/640?wx_fmt=jpeg)\n",
    "\n",
    "mac 配置 image 路径\n",
    "\n",
    "上图中还可以看到 docker 其它的一些资源使用信息如 cpu、内存、磁盘也是在此配置的。\n",
    "\n",
    "第二种方法是建立软链接：就是先把这个文件夹移动到其他（你的其他硬盘或分区）位置，然后再将新位置创建一个软链接到这里来。具体来说：\n",
    "\n",
    "第一步：将 docker 文件夹复制到新位置，由于 socket 文件是不允许直接复制的，可以用快速增量备份工具 rsync 来复制，并将原来文件夹重命名为 com.docker.docker.old：\n",
    "\n",
    "```\n",
    "rsync -a ~/Library/Containers/com.docker.docker/ /Volumes/path/xxx\n",
    "mv com.docker.docker com.docker.docker.old\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "第二步：将新路径软链接过来：\n",
    "\n",
    "```\n",
    "cd ~/Library/Containers\n",
    "ln -s /Volumes/path/xxxx com.docker.docker\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "第三步：启动 docker，如果正常启动，就删除掉原来的 docker 文件夹 com.docker.docker.old。\n",
    "\n",
    "**如果是 linux** 第一种方法就是通过修改 docker 的配置文件来指定镜像的存放位置:\n",
    "\n",
    "```\n",
    "# 停止 docker\n",
    "sudo service docker stop\n",
    "\n",
    "# 编辑文件 docker-overlay.conf\n",
    "cd /etc/systemd/system/docker.service.d # 如果没有docker.service.d 则创建该路径\n",
    "sudo vim docker-overlay.conf  # 如果没有则创建该文件\n",
    "# 在文件中添加如下内容：\n",
    "#  \\[Service\\]\n",
    "#  ExecStart=\n",
    "#  ExecStart=/usr/bin/dockerd --graph=\"新的存储路径\" --storage-driver=overlay\n",
    "\n",
    "# 启动 docker\n",
    "sudo service docker start\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "第二种方法和 mac 的类似，不再赘述。\n",
    "\n",
    "#### 步骤 3：运行容器\n",
    "\n",
    "如果说容器是一个水杯，那镜像就是水，但这个水不会因倒入水杯而减少。运行容器的过程可以比作将水倒入水杯，做各种加工后对外提供服务的过程。\n",
    "\n",
    "现在我们将前述 ubuntu 镜像装入一个新的容器：\n",
    "\n",
    "```\n",
    "docker run ubuntu\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "命令里的 ubuntu 即执行 `docker images` 中对应的 REPOSITORY 名称。\n",
    "\n",
    "使用 `docker ps -a`  查看已运行的容器信息\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialUZh1ibslcwwPPskiajdnCmGmIvPkGhbWNWJVaESwubCicxZZE1IByXMEzA/640?wx_fmt=jpeg)\n",
    "\n",
    "docker ps -a\n",
    "\n",
    "可以看到容器的 id，所属镜像，执行的命令、创建的时间、状态、端口、名称等信息，这里的状态为退出，是因为我们启动容器时没有指定持续运行的进程，因此容器会很快自动退出。\n",
    "\n",
    "运行容器时可以指定容器的名称，端口映射，路径映射，MAC 地址等信息。\n",
    "\n",
    "比如以交互的方式运行一个新 ubuntu 容器，并指定它的名称为 my-ubuntu，主机名为 ubuntu:\n",
    "\n",
    "```\n",
    "docker run -it --hostname=ubuntu --name=my-ubuntu ubuntu /bin/bash\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "执行后，你将以 root 身份进入容器内部，现在你已经进入了 ubuntu 的环境，除了没有图形界面，其他操作没有区别，如下所示：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker run -it --hostname=ubuntu --name=my-ubuntu ubuntu /bin/bash\n",
    "root@ubuntu:/# whoami\n",
    "root\n",
    "root@ubuntu:/# uname -a\n",
    "Linux ubuntu 4.19.76-linuxkit #1 SMP Tue May 26 11:42:35 UTC 2020 x86\\_64 x86\\_64 x86\\_64 GNU/Linux\n",
    "root@ubuntu:/# ls\n",
    "bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var\n",
    "root@ubuntu:/# apt update\n",
    "Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease \\[107 kB\\]\n",
    "Get:2 http://archive.ubuntu.com/ubuntu focal InRelease \\[265 kB\\]\n",
    "Get:3 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 Packages \\[1078 B\\]\n",
    "Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease \\[111 kB\\]\n",
    "Get:5 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages \\[245 kB\\]\n",
    "Get:6 http://archive.ubuntu.com/ubuntu focal-backports InRelease \\[98.3 kB\\]\n",
    "......\n",
    "Fetched 14.6 MB in 32s (452 kB/s)\n",
    "Reading package lists... Done\n",
    "Building dependency tree\n",
    "Reading state information... Done\n",
    "2 packages can be upgraded. Run 'apt list --upgradable' to see them.\n",
    "root@ubuntu:/#\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "输入`exit` 退出容器，如果想再次进入该容器，先使用 `docker ps -a` 查看容器状态，如果容器状态为退出，则需要先使用 `docker start` 启动容器，再使用 exec 命令进入一个正在运行的容器。\n",
    "\n",
    "```\n",
    "root@ubuntu:/# exit\n",
    "exit\n",
    "(py38env) ➜  ~ docker ps -a\n",
    "CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS                      PORTS                    NAMES\n",
    "8a289fba83f7        ubuntu                          \"/bin/bash\"              6 minutes ago       Exited (0) 5 seconds ago                             my-ubuntu\n",
    "9ff5f71f2327        ubuntu                          \"/bin/bash\"              16 minutes ago      Exited (0) 16 minutes ago                            kind\\_merkle\n",
    "b3aa5185fa53        wingszzy/redisall               \"/bin/sh -c /start.sh\"   29 hours ago        Up 29 hours                 0.0.0.0:6379->6379/tcp   romantic\\_nobel\n",
    "9da7144ffe20        kali                            \"/bin/bash\"              32 hours ago        Up 30 hours                 0.0.0.0:8888->8888/tcp   agitated\\_darwin\n",
    "(py38env) ➜  ~ docker exec -it 8a289fba83f7 /bin/bash\n",
    "Error response from daemon: Container 8a289fba83f7bc4e777947e3dc429d871aa3bce67c1418698f9bb04c96f00403 is not running\n",
    "(py38env) ➜  ~ docker start my-ubuntu\n",
    "my-ubuntu\n",
    "(py38env) ➜  ~ docker exec -it 8a289fba83f7 /bin/bash\n",
    "root@ubuntu:/#\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "上述最后的命令中容器 id 也可以替换为容器名称，方便记忆，如：\n",
    "\n",
    "```\n",
    "docker exec -it my-ubuntu /bin/bash\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "#### 步骤 4：复制文件，路径映射\n",
    "\n",
    "docker 提供了 cp 命令来实现容器与本机操作系统间的文件或文件夹传输，非常便捷，如：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker cp key.txt my-ubuntu:/root\n",
    "(py38env) ➜  ~ docker exec -it my-ubuntu /bin/bash\n",
    "root@ubuntu:/# ls /root\n",
    "key.txt\n",
    "root@ubuntu:/# ll /root\n",
    "total 24\n",
    "drwx------ 1 root root    4096 Sep 23 11:30 ./\n",
    "drwxr-xr-x 1 root root    4096 Sep 23 11:30 ../\n",
    "-rw------- 1 root root      55 Sep 23 11:25 .bash\\_history\n",
    "-rw-r--r-- 1 root root    3106 Dec  5  2019 .bashrc\n",
    "-rw-r--r-- 1 root root     161 Dec  5  2019 .profile\n",
    "-rw-r--r-- 1  501 dialout  353 Sep 20 09:49 key.txt\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "反过来也是一样的，命令的格式如下：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker help cp\n",
    "\n",
    "Usage:    docker cp \\[OPTIONS\\] CONTAINER:SRC\\_PATH DEST\\_PATH|-\n",
    "    docker cp \\[OPTIONS\\] SRC\\_PATH|- CONTAINER:DEST\\_PATH\n",
    "\n",
    "Copy files/folders between a container and the local filesystem\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "你也许会想拷贝来拷贝去真麻烦，有没有更简单的方法，当然有，那就是路径映射，比如将本地的 /Users/aaron/xxx 映射至 docker 的 /root/path/yyy ，那么本质上 /Users/aaron/xxx 和 /root/path/yyy 就是同一个文件夹，就省去了来回复制的麻烦。具体操作就是第一次启动容器时指定，注意也就是 `docker run` 命令时指定。\n",
    "\n",
    "比如\n",
    "\n",
    "`docker run -it --hostname=ubuntu2 --name=my-ubuntu2 -v /Users/aaron/:/root/ ubuntu /bin/bash`\n",
    "\n",
    "会启动一个新的容器，如下图所示：\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialUYKv2icTDb6eSLXQGSQrS2uTHpwvrUk1fCeNcxfURvWnNfVjcMgAo7Eg/640?wx_fmt=jpeg)\n",
    "\n",
    "路径映射\n",
    "\n",
    "#### 步骤 5：网络配置、端口映射\n",
    "\n",
    "默认情况下，容器可以访问宿主机（安装 docker 的电脑）及宿主机外部的网络上的服务，但宿主机及外部网络无法访问容器内部的服务，这是为什么呢？\n",
    "\n",
    "回答这个问题需要先了解 docker 的网络，如果你了解虚拟机的网络，那么对比着理解即可。\n",
    "\n",
    "创建 docker 容器时，可以用 `--network string` 参数来指定四种网络模式：bridge（默认）、host、none、container。执行 `docker network ls` 可以看出 (其中 container 未列出)：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker network ls\n",
    "NETWORK ID          NAME                DRIVER              SCOPE\n",
    "a3b7e595cd4e        bridge              bridge              local\n",
    "b49f580dfd98        host                host                local\n",
    "63ce10ba830f        none                null                local\n",
    "(py38env) ➜  ~\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "1、bridge，即桥接模式，docker run 的默认模式。\n",
    "\n",
    "此模式会为容器分配 network namespace、设置 IP 等， 并将容器桥接到一个虚拟网桥 docker0 (docker0 只是个名字，后续版本就叫 bridge0) 上，可以和同一宿主机上桥接模式的其他容器进行通信。docker0 是 docker 在启动时在宿主机器上创建的虚拟网络接口，它会从 RFC 1918 定义的私有地址中随机选择一个主机不用的地址和子网掩码，并将它分配给 docker0，默认选择 172.18.0.1/16，一个 16 位的子网掩码给容器提供了 65534 个 IP 地址。docker0 并不是正常的网络接口，只是一个在绑定到这上面的其他网卡间自动转发数据包的虚拟以太网桥，可以使容器与主机相互通信、容器与容器间相互通信。\n",
    "\n",
    "此模式可以理解为 docker 自己有个内部局域网环境，如果说 docker0 是个 wifi 信号的话，那这些容器就是连接这个热点的电脑。因此，容器可以访问宿主机（安装 docker 的电脑）及宿主机外部的网络上的服务，但宿主机及外部网络无法访问容器内部的服务。\n",
    "\n",
    "但是，可以通过端口映射的方式，让外网访问容器某些端口。比如运行容器\n",
    "\n",
    "`docker run -it -p 38022:22 --name='my-ubuntu-3' ubuntu /bin/bash` 之后，容器 my-ubuntu-3 的 22 端口被映射在了宿主机的 38022 端口，我们可以直接 `ssh root@localhost -p 38022` 来进入容器。如果要登陆成功，这里有个前提， ubuntu 容器已经开启 ssh 服务，允许 root ssh 远程登陆，且你知道 root 密码。\n",
    "\n",
    "在其他服务器上通过访问宿主机 ip 地址加端口即可访问容器，还可以一次映射多个端口。运行容器 `docker run -it -p 38022:22 -p 38080:80 --name='my-ubuntu-4' ubuntu /bin/bash`\n",
    "\n",
    "端口映射还可使用`-P`来实现将容器内部开放的网络端口随机映射到宿主机的一个端口上\n",
    "\n",
    "命令 `docker port container_ID` 可以查看容器的端口映射情况：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker port 805d8ef4b504\n",
    "22/tcp -> 0.0.0.0:38022\n",
    "(py38env) ➜  ~\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "2、host，主机模式。\n",
    "\n",
    "主机模式下，docker 共享主机的网络命名空间，也就是说`--network host` 表示容器与宿主机共用网卡、路由等。并且该容器不会分配自己的 IP 地址。如果在容器中运行一个监听端口 80 的应用，则该容器的应用在宿主机主机 IP 地址上的端口 80 上可用。\n",
    "\n",
    "例如，我们在 10.10.101.105/24 的机器上用 host 模式启动一个含有 web 应用的 docker 容器，监听 tcp80 端口。当我们在容器中执行任何类似 ifconfig 命令查看网络环境时，看到的都是宿主机上的信息。而外界访问容器中的应用，则直接使用 10.10.101.105:80 即可，不用任何 NAT 转换，就如直接跑在宿主机中一样。但是，容器的其他方面，如文件系统、进程列表等还是和宿主机隔离的。\n",
    "\n",
    "如果您 host 对容器使用网络模式，则该容器的网络堆栈不会与 Docker 主机隔离（该容器共享主机的网络名称空间），并且该容器不会分配自己的 IP 地址。例如，如果您运行一个绑定到端口 80 host 的容器并使用网络，则该容器的应用程序在主机 IP 地址上的端口 80 上可用。\n",
    "\n",
    "host 模式下，容器不拥有自己的 IP 地址 端口映射不生效，并且 -p，--publish，-P，和 --publish-all 选项都将被忽略\n",
    "\n",
    "主机模式网络对于优化性能以及在容器需要处理大量端口的情况下很有用，因为它不需要网络地址转换（NAT），并且不会为每个端口创建 “userland-proxy”。\n",
    "\n",
    "主机模式仅适用于 Linux 主机，而 Mac 的 Docker Desktop，Windows 的 DockerDesktop 或 Windows Server 的 Docker EE 不支持。\n",
    "\n",
    "3、container，容器模式。\n",
    "\n",
    "跟 host 模式类似，只不过其网络命名空间共享的对象为一个容器而非宿主机。\n",
    "\n",
    "4、none。\n",
    "\n",
    "none 表示容器默认有自己的 network namespace，但不为 docker 容器进行任何网络配置， 这将所有网络创建操作完全自定义，实现更加灵活复杂的网络。\n",
    "\n",
    "#### 步骤 6：自定义镜像：保存修改后的容器至新的镜像\n",
    "\n",
    "命令：`docker commit 容器id 镜像名称`\n",
    "\n",
    "只要我们不执行 `docker rm` 来删除容器，那么对该容器的修改一直有效，但通常情况下，我们对容器作出一些修改，是为了给别人用，比如将自己的程序部署在容器中打包成镜像，目的是为了发布出去方便别人直接使用。那么就需要将对容器的修改保存为新的镜像。\n",
    "\n",
    "例如本例子中：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker exec -it my\\_host /bin/bash\n",
    "root@docker-desktop:/# ls\n",
    "bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var\n",
    "root@docker-desktop:/# cd root\n",
    "root@docker-desktop:~# ls\n",
    "root@docker-desktop:~# touch saved.txt\n",
    "root@docker-desktop:~# ls\n",
    "saved.txt\n",
    "root@docker-desktop:~# exit\n",
    "exit\n",
    "(py38env) ➜  ~ docker commit my\\_host my\\_ubuntu\\_image\n",
    "sha256:3ae070a40b8da2411a0b212e8e78a7f02a19820fd45f67288b436c427316e34e\n",
    "(py38env) ➜  ~ docker images\n",
    "REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE\n",
    "my\\_ubuntu\\_image          latest              3ae070a40b8d        4 seconds ago       171MB\n",
    "kali                     latest              c9c07bf9ce32        2 days ago          1.74GB\n",
    "ubuntu                   latest              bb0eaf4eee00        7 days ago          72.9MB\n",
    "(py38env) ➜  ~ docker run -it my\\_ubuntu\\_image /bin/bash\n",
    "root@114112c6194d:/# ll /root/\n",
    "total 24\n",
    "drwx------ 1 root root 4096 Sep 24 07:20 ./\n",
    "drwxr-xr-x 1 root root 4096 Sep 24 07:20 ../\n",
    "-rw------- 1 root root   38 Sep 24 07:20 .bash\\_history\n",
    "-rw-r--r-- 1 root root 3106 Dec  5  2019 .bashrc\n",
    "-rw-r--r-- 1 root root  161 Dec  5  2019 .profile\n",
    "-rw-r--r-- 1 root root    0 Sep 24 07:19 saved.txt\n",
    "root@114112c6194d:/#\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "#### 步骤 7：发布镜像\n",
    "\n",
    "1、先在 https://hub.docker.com/ 上注册一个账号，并创建一个仓库，比如长这样：\n",
    "\n",
    "![](https://mmbiz.qpic.cn/mmbiz_jpg/EnE7vpEWFnoUmLcojicuHGf29ca2HRialU4M7nQVgxV8icZypxsGZk6IyFPmFnz8745ALBn64gJfwAnIJgdC0sAyw/640?wx_fmt=jpeg)\n",
    "\n",
    "创建一个 docker 仓库\n",
    "\n",
    "2、使用第 1 步的账号密码在 Docker Desktop 上登陆。\n",
    "\n",
    "3、注意，我们创建的仓库是 somenzz/my-kali，因此先执行 `docker tag my-kali somenzz/my-kali` 给已存在的镜像打个标签，然后执行 `docker push somenzz/my-kali` 上传本地镜像。\n",
    "\n",
    "4、如果是公开的仓库，其他人执行`docker pull somenzz/my-kali` 即可下载你上传的镜像来使用。\n",
    "\n",
    "执行细节如下：\n",
    "\n",
    "```\n",
    "(py38env) ➜  ~ docker tag my-kali somenzz/my-kali\n",
    "(py38env) ➜  ~ docker images\n",
    "REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE\n",
    "my-kali                  latest              975cbe3a4619        14 minutes ago      1.83GB\n",
    "somenzz/my-kali          latest              975cbe3a4619        14 minutes ago      1.83GB\n",
    "my\\_ubuntu\\_image          latest              3ae070a40b8d        24 minutes ago      171MB\n",
    "kali                     latest              c9c07bf9ce32        2 days ago          1.74GB\n",
    "ubuntu                   latest              bb0eaf4eee00        7 days ago          72.9MB\n",
    "(py38env) ➜  ~ docker push somenzz/my-kali\n",
    "The push refers to repository \\[docker.io/somenzz/my-kali\\]\n",
    "4ed57b65cfb6: Pushed\n",
    "8baabf99a4c6: Pushing  1.629GB/1.629GB\n",
    "8e0f65c626fe: Pushing  102.7MB/113.8MB\n",
    "......\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "#### 步骤 8：管理本地镜像和容器\n",
    "\n",
    "将指定镜像保存成 tar 归档文件，用于持久化镜像\n",
    "\n",
    "```\n",
    "docker save -o my\\_ubuntu.tar ubuntu\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "从归档文件中创建镜像\n",
    "\n",
    "```\n",
    "docker import my\\_ubuntu.tar my-ubuntu\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "将指定容器的文件系统保存成 tar 归档文件，用于持久化容器\n",
    "\n",
    "```\n",
    "docker export containerID/containerName > /home/export.tar\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "列出所有的容器\n",
    "\n",
    "```\n",
    "docker ps -a\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "列出最近一次启动的容器\n",
    "\n",
    "```\n",
    "docker ps -l\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "检查镜像\n",
    "\n",
    "```\n",
    "docker inspect image-name\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "检查容器\n",
    "\n",
    "```\n",
    "docker inspect container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "获取容器的相关信息:\n",
    "\n",
    "获取容器 CID\n",
    "\n",
    "```\n",
    "docker inspect -f '{{.Id}}' container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "获取容器 PID\n",
    "\n",
    "```\n",
    "docker inspect -f '{{.State.Pid}}' container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "获取容器 IP\n",
    "\n",
    "```\n",
    "docker inspect -f '{{.NetworkSettings.IPAddress}}' container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "获取容器网关\n",
    "\n",
    "```\n",
    "docker inspect -f '{{.NetworkSettings.Gateway}}' container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "获取容器 MAC\n",
    "\n",
    "```\n",
    "docker inspect -f '{{.NetworkSettings.MacAddress}}' container-name/containerID\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "停止容器\n",
    "\n",
    "```\n",
    "docker stop containerID/container-name\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "停止所有容器\n",
    "\n",
    "```\n",
    "docker kill $(docker ps -a -q)\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "启动容器\n",
    "\n",
    "```\n",
    "docker start containerID/container-name\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "删除单个正在运行的容器，删除容器之前要先停止该容器的运行\n",
    "\n",
    "```\n",
    "docker stop containerID/container-name\n",
    "docker rm containerID/container-name\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "删除所有容器\n",
    "\n",
    "```\n",
    "docker kill $(docker ps -a -q) docker rm $(docker ps -a -q)\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "删除镜像\n",
    "\n",
    "```\n",
    "docker rmi image-name\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "#### 系统性的 docker 学习资料\n",
    "\n",
    "分享一些可以系统学习 Docker 的地方，对于新手来说很有帮助。这些都是权威科学的学习教程，不必记录网址，记录中文名称，搜索引擎搜索即可。\n",
    "\n",
    "1.  docker 官方文档: https://docs.docker.com/\n",
    "    \n",
    "2.  docker 官方博客: https://www.docker.com/blog/\n",
    "    \n",
    "3.  docker 中文社区: https://www.docker.org.cn/\n",
    "    \n",
    "4.  动手玩 docker：网易云免费课堂\n",
    "    \n",
    "\n",
    "#### 容器比虚拟机的优势\n",
    "\n",
    "由于容器是进程级别的，相比虚拟机有很多优势。（1）启动快：容器里面的应用，直接就是底层系统的一个进程，而不是虚拟机内部的进程。所以，启动容器相当于启动本机的一个进程，而不是启动一个操作系统，速度就快很多。（2）资源占用少 ：容器只占用需要的资源，不占用那些没有用到的资源；虚拟机由于是完整的操作系统，不可避免要占用所有资源。另外，多个容器可以共享资源，虚拟机都是独享资源。（3）体积小：容器只要包含用到的组件即可，而虚拟机是整个操作系统的打包，所以容器文件比虚拟机文件要小很多。总之，容器有点像轻量级的虚拟机，能够提供虚拟化的环境，但是成本开销小得多\n",
    "\n",
    "#### 小结\n",
    "\n",
    "本文从使用 docker 的整个生命周期的顺序进行讲述，方便理解和记忆：从安装 docker 软件，下载镜像，启动容器，文件传输，网络设置，端口映射，保存容器到镜像，发布镜像等应有仅有，值得入门学习和收藏。\n",
    "\n",
    "（完）\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
